#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <bits/endian.h>

#include <ZLog.h>
#include <YuvIoctl.h>

#pragma pack(1)
/*bitmap-file header*/
struct BitmapHeaderS
{
    char identifier[2];
    uint32_t fileSize;
    char reserved[4];
    uint32_t dataOffset;
    uint32_t hdrSize;
    uint32_t width;
    uint32_t height;
    uint16_t planes;
}; /* 28byte */

/*bitmap-information heade*/
struct BitmapInformationHeaderS
{
    uint16_t bitPerPix;
    uint32_t compression;
    uint32_t bitmapDataSize;
    uint32_t hResolution;
    uint32_t vResolution;
    uint32_t colors;
    uint32_t importantColors;

}; /* 26byte */
#pragma pack()

uint32_t htolel(uint32_t n)
{
#if (__BYTE_ORDER == __LITTLE_ENDIAN)
    return n;
#elif (__BYTE_ORDER == __BIG_ENDIAN)
    /*
     * 0x12345678
     * n :   '12 34 56 78' big-endian
     * ret : '78 56 34 12' little-endian
     */
    uint32_t ret = 0;
    uint8_t *bFrom, *bTo;
    bFrom = (uint8_t *)&n;
    bTo = (uint8_t *)&ret;
    bTo[0] = bFrom[3];
    bTo[1] = bFrom[2];
    bTo[2] = bFrom[1];
    bTo[3] = bFrom[0];
    return ret;
#else
#error "Unknown byte order"
#endif
}

uint16_t htoles(uint16_t n)
{
#if (__BYTE_ORDER == __LITTLE_ENDIAN)
    return n;
#elif (__BYTE_ORDER == __BIG_ENDIAN)
    /*
     * 0x1234
     * n :   '12 34 ' big-endian
     * ret : '34 12' little-endian
     */
    uint16_t ret = 0;
    uint8_t *bFrom, *bTo;
    bFrom = (uint8_t *)&n;
    bTo = (uint8_t *)&ret;
    bTo[0] = bFrom[1];
    bTo[1] = bFrom[0];
    return ret;
#else
#error "Unknown byte order"
#endif
}

int YuvToRgb(int y, int u, int v, uint8_t *rgb)
{
    int r, g, b;
    
    r = (int)((y&0xff) + 1.4075 * ((v&0xff)-128));  
    g = (int)((y&0xff) - 0.3455 * ((v&0xff)-128) - 0.7169*((v&0xff)-128));  
    b = (int)((y&0xff) + 1.779 * ((u&0xff)-128)); 
    r = (r<0? 0: r>255? 255 : r);  
    g = (g<0? 0: g>255? 255 : g);  
    b = (b<0? 0: b>255? 255 : b);

    rgb[0] = (uint8_t)r;
    rgb[1] = (uint8_t)g;
    rgb[2] = (uint8_t)b;
    return 0;
}       

int Yuv2Bmp(char *yuvFile, uint32_t planar, int picW, int picH, int datW, int datH, 
            char *bmpFile)
{
    
    YuvIoT *yuv = NULL;
    FILE *bmp = NULL;
    struct BitmapHeaderS bmpHdr;
    struct BitmapInformationHeaderS bmpInfoHdr;
    int ret;
    
    memset(&bmpHdr, 0x00, sizeof(bmpHdr));
    memset(&bmpInfoHdr, 0x00, sizeof(bmpInfoHdr));
    
    // TODO: only support NV21
    yuv = YuvIoInstance(yuvFile, picW, picH, datW, datH, YUV_PLANAR_NV21);
    Z_ASSERT(yuv);

    bmp = fopen(bmpFile, "w+");
    
    bmpHdr.identifier[0] = 'B';
    bmpHdr.identifier[1] = 'M';
    bmpHdr.fileSize = htolel(picW*picH*3 + sizeof(bmpHdr) + sizeof(bmpInfoHdr));
//    bmpHdr.reserved = 0;
    bmpHdr.dataOffset = htolel(sizeof(bmpHdr) + sizeof(bmpInfoHdr));
    bmpHdr.hdrSize = htolel(40);
    bmpHdr.width = htolel(picW);
    bmpHdr.height = htolel(picH);
    bmpHdr.planes = htoles(1);
    ret = fwrite(&bmpHdr, 1, sizeof(bmpHdr), bmp);
    Z_ASSERT(ret == sizeof(bmpHdr));

    bmpInfoHdr.bitPerPix = htoles(24);
    ret = fwrite(&bmpInfoHdr, 1, sizeof(bmpInfoHdr), bmp);
    Z_ASSERT(ret == sizeof(bmpInfoHdr));

    uint8_t *rgbs = malloc(3*picW);
    int x = 0, y =0;
    int Y, U, V;
    for (y = picH - 1; y >= 0; y--)
    {
        for (x = 0; x < picW; x++)
        {
            Y = yuv->ops->readY(yuv, x, y);
            U = yuv->ops->readU(yuv, x, y);
            V = yuv->ops->readV(yuv, x, y);
            YuvToRgb(Y, U, V, rgbs + x*3);
            
        }
        ret = fwrite(rgbs, 3, picW, bmp);
        Z_ASSERT(ret == picW);
    }
    fclose(bmp);
    free(rgbs);
    return 0;
}

/* ./yuv2bmp [picW] [picH] [datW] [datH] [fourCC] [path] [bmp]*/
int main(int argc, char *argv[])
{
    int picW, picH, datW, datH;
    uint32_t fourCC;
    char *path;
    char *bmp;
    int ret;
    
    Z_ASSERT(argc == 8);
    
    picW = atoi(argv[1]);
    picH = atoi(argv[2]);
    datW = atoi(argv[3]);
    datH = atoi(argv[4]);
    memcpy(&fourCC, argv[5], 4);
    path = argv[6];
    bmp = argv[7];
    
    ret = Yuv2Bmp(path, fourCC, picW, picH, datW, datH, bmp);
    LogI("Finish! Store: '%s'", bmp);

    return ret;
}

